#region License, Terms and Conditions
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
#endregion
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using Jayrock.Json.Conversion;
using Jayrock.Json;
using Pesta.DataAccess.Azure;
using Pesta.DataAccess.SqlServer;
using raya.Library;
using raya.Models.people;

namespace raya.Models.applications
{
    public class gadgetInfo
    {
        public ProfileApplication gadget { get; set; }
        public int width { get; set; }
        public string view{ get; set; }
        public string personid{ get; set; }
        public bool has_app{ get; set; }
    }
#if AZURE
    public class Application
    {
        public ApplicationRow application { get; set; }
        public string error { get; set; }
        public oauth.oauth.OAuthConsumerInfo oauth { get; set; }
    }
#else
    public class Application
    {
        public application application { get; set; }
        public string error { get; set; }
        public oauth.oauth.OAuthConsumerInfo oauth { get; set; }
    }
#endif

    public class ApplicationAdded
    {
        public string app_id { get; set; }
        public string mod_id { get; set; }
        public string error { get; set; }
    }
    public class ApplicationCanvas : ProfileBase
    {
        public ProfileApplication application { get; set; }
    }

    public class applications : Model
    {
        public enum appType
        {
            external = 0,
            raya = 1
        }
#if AZURE
        public void remove_application_permanent(string appid)
        {
            using (var db = RayaDataContext.DB())
            {
                // delete apps
                var app = db.applications.Where(a => a.id == appid && 
                                                    a.type == (int)appType.external).SingleOrDefault();
                if (app == null)
                {
                    return;
                }
                db.DeleteOnSubmit(app);
                db.SubmitChanges();
            }
        }

        public List<Application> get_all_applications()
        {
            var apps = new List<Application>();
            using (var db = RayaDataContext.DB())
            {
                var result = db.applications.Where(x => x.type == (int) appType.external);
                foreach (var row in result)
                {
                    var app = new Application
                    {
                               application = row,
                               oauth = oauth.oauth.get_gadget_consumer(row.id.ToString())
                    };
                    apps.Add(app);
                }
                apps = apps
                    .OrderBy(x => x.application.directory_title)
                    .OrderBy(x => x.application.title)
                    .ToList();
            }

            return apps;
        }

        public ApplicationAdded add_application(string personId, string appUrl, appType type)
        {
            string modId = "";
            Application app = get_application(appUrl, type);
            string appId = app.application.id;
            string error = app.error;
            if (!string.IsNullOrEmpty(appId) && string.IsNullOrEmpty(error))
            {
                PersonApplicationRow personapp = new PersonApplicationRow(personId,appId,app.application);
                using (var db = RayaDataContext.DB())
                {
                    db.InsertOnSubmit(AzureRayaDataContext.TableNames.personApplications, personapp);
                    db.SubmitChanges();
                    modId = personapp.mod_id;
                }
            }
            ApplicationAdded retVal = new ApplicationAdded { app_id = appId, mod_id = modId, error = error };
            return retVal;
        }

        public Application get_application_by_id(string id)
        {
            using (var db = RayaDataContext.DB())
            {
                var result = db.applications.Where(x => x.id == id).SingleOrDefault();
                if (result == null)
                {
                    return null;
                }
                return get_application(result.url);
            }
        }

        public bool remove_application(string personId, string appId, string mid)
        {
            using (var db = RayaDataContext.DB())
            {
                var personapp = (from p in db.personApplications
                                where p.id == appId && p.person_id == personId && p.mod_id == mid
                                select p).SingleOrDefault();
                db.DeleteOnSubmit(personapp);
                db.SubmitChanges();
                return true;
            }
        }

        public List<ProfileApplication> get_person_applications(string pid)
        {
            var apps = new List<ProfileApplication>();
            using (var db = RayaDataContext.DB())
            {
                var results = db.personApplications.Where(x => x.type != (int) appType.raya && x.PartitionKey == pid);
                
                foreach (var entry in results)
                {
                    var app = new ProfileApplication
                                  {
                                      app = new Application {application = entry},
                                      mod_id = pid,
                                      user_prefs = get_application_prefs(pid, entry.id),
                                      oauth = oauth.oauth.get_person_consumer(entry.id)
                                  };
                    apps.Add(app);
                }
            }
            return apps;
        }

        public ProfileApplication get_person_application(string pid, string aid, string mid)
        {
            ProfileApplication profileApp = new ProfileApplication();
            using (var db = RayaDataContext.DB())
            {
                var result = db.applications.Where(x => x.RowKey == aid).SingleOrDefault();
                if (result != null)
                {
                    profileApp.app = get_application(result.url);
                }
                profileApp.mod_id = mid;
                profileApp.user_prefs = get_application_prefs(pid, aid);
            }
           
            return profileApp;
        }

        public NameValueCollection get_application_prefs(string personId, string appId)
        {
            NameValueCollection data = new NameValueCollection();
            using (var db = RayaDataContext.DB())
            {
                var result = db.applicationSettings
                    .Where(x => x.PartitionKey == personId && x.application_id == appId);

                foreach (var res in result)
                {
                    data.Add(res.name, res.value);
                }
            }
            return data;
        }

        public void set_application_pref(string personId, string appId, string key, string value)
        {
            string rowkey = string.Concat(appId, "_", personId, "_", key);
            using (var db = RayaDataContext.DB())
            {
                var existing = db.applicationSettings
                                    .Where(a => a.RowKey == rowkey &&
                                                a.PartitionKey == personId)
                                    .SingleOrDefault();
                if (existing == null)
                {
                    var settings = new ApplicationSettingRow(personId, rowkey)
                                       {
                                           application_id = appId,
                                           person_id = personId,
                                           name = key,
                                           value = value
                                       };
                    db.InsertOnSubmit(AzureRayaDataContext.TableNames.applicationSettings, settings);
                }
                else
                {
                    existing.value = value;
                    db.UpdateOnSubmit(existing);
                }
                db.SubmitChanges();
            }
        }

        private Application get_application(string url, appType type)
        {
            string error = "";
            ApplicationRow info = new ApplicationRow();
            Application app = new Application();
            // see if we have up-to-date info in our db. Cut-off time is 1 day (aka refresh module info once a day)
            long time = DateTime.UtcNow.AddDays(-1).Ticks;
            string key = OAuthUtil.md5(url);
            using (var db = RayaDataContext.DB())
            {
                var appi = db.applications.Where(x => x.PartitionKey == key && x.modified > time).SingleOrDefault();
                if (appi != null)
                {
                    // we have an entry with up-to-date info
                    info = appi;
                }
                else
                {
                    // Either we dont have a record of this module or its out of date, so we retrieve the app meta data.
                    var response = fetch_gadget_metadata(url);
                    if (response == null)
                    {
                        // invalid json object, something bad happened on the shindig metadata side.
                        error = "An error occured while retrieving the gadget information";
                    }
                    else
                    {
                        // valid response, process it
                        var gadgets = (JsonArray)response["gadgets"];
                        var gadget = (JsonObject)gadgets[0];
                        if (!String.IsNullOrEmpty(gadget["errors"] as string))
                        {
                            // failed to retrieve gadget, or failed parsing it
                            error = gadget["errors"] as string;
                        }
                        else
                        {
                            // retrieved and parsed gadget ok, store it in db
                            var update = true;
                            info = db.applications.Where(a => a.PartitionKey == key).SingleOrDefault();
                            if (info == null)
                            {
                                update = false;
                                info = new ApplicationRow(key, DateTime.UtcNow.Ticks.ToString());
                            }
                            info.url = (string)gadget["url"];
                            info.title = gadget["title"] == null ? "" : (string)gadget["title"];
                            info.directory_title = gadget["directoryTitle"] == null ? "" : (string)gadget["directoryTitle"];
                            info.height = string.IsNullOrEmpty(gadget["height"] as string) ? 0 : int.Parse(gadget["height"].ToString());
                            info.screenshot = gadget["screenshot"] == null ? "" : (string)gadget["screenshot"];
                            info.thumbnail = gadget["thumbnail"] == null ? "" : (string)gadget["thumbnail"];
                            info.author = gadget["author"] == null ? "" : (string)gadget["author"];
                            info.author_email = gadget["author_email"] == null ? "" : (string)gadget["author_email"];
                            info.description = gadget["description"] == null ? "" : (string)gadget["description"];
                            info.settings = gadget["userPrefs"] == null ? "" : gadget["userPrefs"].ToString();
                            info.views = gadget["views"] == null ? "" : gadget["views"].ToString();
                            info.scrolling = gadget["scrolling"] == null ? "no" : gadget["scrolling"].ToString() == "true" ? "yes" : "no";
                            info.type = (int)type;
                            // extract the version from the iframe url
                            string iframeUrl = (string)gadget["iframeUrl"];
                            info.version = "";
                            if (!String.IsNullOrEmpty(iframeUrl) && iframeUrl.IndexOf('?') != -1)
                            {
                                var iframeParsed = HttpUtility.ParseQueryString(iframeUrl.Substring(iframeUrl.IndexOf('?') + 1));
                                info.version = iframeParsed.Get("v");
                            }
                            info.modified = DateTime.UtcNow.Ticks;
                            if (update)
                            {
                                db.UpdateOnSubmit(info);
                            }
                            else
                            {
                                db.InsertOnSubmit(AzureRayaDataContext.TableNames.applications, info);
                            }
                            db.SubmitChanges();
                        }
                    }
                }
            }

            app.application = info;
            app.error = error;

            return app;
        }
#else
        public List<ProfileApplication> get_person_applications(int pid)
        {
            object data;
            if (!TryGetCache("person_applications", pid.ToString(), out data))
            {
                add_dependency("person_applications", pid.ToString(), null);
                using (var db = RayaDataContext.DB())
                {
                    var ret = from p in db.person_applications
                               join a in db.applications on p.application_id equals a.id
                               where p.person_id == pid && a.type != (int)appType.raya
                               select new ProfileApplication
                                          {
                                              app = new Application {application = a},
                                              mod_id = p.id.ToString(),
                                              user_prefs = get_application_prefs(pid, a.id),
                                              oauth = oauth.oauth.get_person_consumer(a.id)
                                          };
                    foreach (var entry in ret)
                    {
                        add_dependency("applications", entry.app.application.id.ToString(), "person_applications_res:" + pid);
                    }
                    data = ret.ToList();
                    Insert("person_applications", pid.ToString(), data);
                }
            }
            return (List<ProfileApplication>)data;
        }

        /// <summary>
        /// Lists all applications on the site
        /// </summary>
        /// <returns></returns>
        public List<Application> get_all_applications()
	    {
            object data;
            if (!TryGetCache("applications", "all", out data))
            {
                add_dependency("applications", "all", null);
                using (var db = RayaDataContext.DB())
                {
                    var oa = new oauth.oauth();
                    var _res = from a in db.applications
                               where a.type == (short)appType.external
                               orderby a.directory_title , a.title
                               select new Application
                                          {
                                              application = a,
                                              oauth = oa.get_gadget_consumer(a.id.ToString())
                                          };
                    data = _res.ToList();
                    Insert("applications", "all", data);
                    oa.Dispose();
                }
            }
            return (List<Application>)data;
	    }

	    public void set_application_pref(int _person_id, int _app_id, string _key, string _value)
	    {
            invalidate_dependency("person_application_prefs", _person_id.ToString());
            using (var db = RayaDataContext.DB())
            {
                application_setting existing =
                    db.application_settings.SingleOrDefault(
                        a => a.application_id == _app_id && a.person_id == _person_id && a.name == _key);
                if (existing == null)
                {
                    existing = new application_setting
                                   {application_id = _app_id, person_id = _person_id, name = _key, value = _value};
                    db.application_settings.InsertOnSubmit(existing);
                }
                else
                {
                    existing.value = _value;
                }
                db.SubmitChanges();
            }
	    }

        public NameValueCollection get_application_prefs(int _person_id, int _app_id)
	    {
            object data;
            if (!TryGetCache("person_application_prefs", _person_id.ToString(), out data))
            {
                add_dependency("person_application_prefs", _person_id.ToString(), null);
                NameValueCollection _res = new NameValueCollection();
                using (var db = RayaDataContext.DB())
                {
                    var result = from a in db.application_settings
                                 where a.application_id == _app_id && a.person_id == _person_id
                                 select new
                                            {
                                                a.name,
                                                a.value
                                            };
                    foreach (var res in result)
                    {
                        _res.Add(res.name,res.value);
                    }
                }
                data = _res;
                Insert("person_application_prefs", _person_id.ToString(), data);
            }
            return (NameValueCollection)data;
	    }

        /// <summary>
        /// Get an application belonging to the user
        /// </summary>
        /// <param name="pid"></param>
        /// <param name="appId"></param>
        /// <param name="modId"></param>
        /// <returns></returns>
        public ProfileApplication get_person_application(string pid, string appId, string modId)
        {
            int personId = int.Parse(pid);
            object data;
            var profileApp = new ProfileApplication();
            if (!TryGetCache("applications", appId.ToString(), out data))
            {
                using (var db = RayaDataContext.DB())
                {
                    var _res = (from a in db.applications
                                where a.id.ToString() == appId
                                select a.url).SingleOrDefault();
                    data = get_application(_res);
                }
                
                //add_dependency("person_applications", null, _person_id.ToString());
                add_dependency("applications", appId.ToString(), null);
                //add_dependency("person_application_prefs", _person_id.ToString(), _app_id.ToString());
                Insert("applications", appId.ToString(), data);
            }
            profileApp.app = (Application)data;
            profileApp.mod_id = modId.ToString();
#if AZURE
            profileApp.user_prefs = get_application_prefs(personId, _app_id);
#else
            profileApp.user_prefs = get_application_prefs(personId, int.Parse(appId));
#endif
            
            return profileApp;
	    }

        public Application get_application_by_id(int id)
	    {
            object data;
            if (!TryGetCache("applications", id.ToString(), out data))
            {
                using (var db = RayaDataContext.DB())
                {
                    var _res = (from a in db.applications
                                where a.id == id
                                select a.url).SingleOrDefault();
                    data = get_application(_res);
                    Insert("applications", id.ToString(), data);
                    //add_dependency("person_applications", _id.ToString());
                    add_dependency("applications", id.ToString(), null);
                }
            }
            return (Application) data;
	    }

        
        

        /// <summary>
        /// Adds application to user profile
        /// </summary>
        /// <param name="personId"></param>
        /// <param name="appUrl"></param>
        /// <returns></returns>
        public ApplicationAdded add_application(int personId, string appUrl, appType type)
        {
            int modId = 0;
            Application data = get_application(appUrl, type);
            int appId = data.application.id;
            string error = data.error;
		    if (appId != 0 && string.IsNullOrEmpty(error)) 
            {
			    // we now have a valid gadget record in _info, with no errors occured, proceed to add it to the person
			    // keep in mind a person -could- have two the same apps on his page (though with different module_id"s) so no
			    // unique check is done.
			    person_application personapp = new person_application {person_id = personId, application_id = appId};
                using (var db = RayaDataContext.DB())
                {
                    db.person_applications.InsertOnSubmit(personapp);
                    db.SubmitChanges();
                    modId = personapp.id;
                }
                invalidate_dependency("person_applications", personId.ToString());
                invalidate_dependency("person_application_prefs", personId.ToString());
		    }
            ApplicationAdded retVal = new ApplicationAdded {app_id = appId.ToString(), mod_id = modId.ToString(), error = error};
            return retVal;
	    }

        /// <summary>
        /// Removes application from user profile
        /// </summary>
        /// <param name="_person_id"></param>
        /// <param name="_app_id"></param>
        /// <param name="_mod_id"></param>
        /// <returns></returns>
	    public bool remove_application(int _person_id, int _app_id, int _mod_id)
	    {
		    using (var db = RayaDataContext.DB())
		    {
		        var personapp = from p in db.person_applications
		                        where p.id == _mod_id && p.person_id == _person_id && p.application_id == _app_id
		                        select p;
                db.person_applications.DeleteOnSubmit(personapp.SingleOrDefault());
                db.SubmitChanges();
                invalidate_dependency("person_applications", _person_id.ToString());
                invalidate_dependency("person_application_prefs", _person_id.ToString());
                return (db.GetChangeSet().Deletes.Count == 0);
		    }
	    }
        public void remove_application_permanent(string appid)
        {
            int id = int.Parse(appid);
            using (var db = RayaDataContext.DB())
            {
                // delete apps
                var app = db.applications.Where(a => a.id == id && a.type == (int) appType.external).SingleOrDefault();
                if (app == null)
                {
                    return;
                }
                db.applications.DeleteOnSubmit(app);
                db.SubmitChanges();
            }
        }

        private Application get_application(string appUrl, appType type)
        {
            string error = "";
            var info = new application();
            Application app = new Application();
            // see if we have up-to-date info in our db. Cut-off time is 1 day (aka refresh module info once a day)
            long time = DateTime.UtcNow.AddDays(-1).Ticks;
            using (var db = RayaDataContext.DB())
            {
                var appi = from a in db.applications
                           where a.url == appUrl && a.modified > time
                           select a;
                if (appi.Count() > 0)
                {
                    // we have an entry with up-to-date info
                    info = appi.SingleOrDefault();
                }
                else
                {
                    // Either we dont have a record of this module or its out of date, so we retrieve the app meta data.
                    var response = fetch_gadget_metadata(appUrl);
                    if (response == null)
                    {
                        // invalid json object, something bad happened on the shindig metadata side.
                        error = "An error occured while retrieving the gadget information";
                    }
                    else
                    {
                        // valid response, process it
                        var gadgets = (JsonArray)response["gadgets"];
                        var gadget = (JsonObject)gadgets[0];
                        if (!String.IsNullOrEmpty(gadget["errors"] as string))
                        {
                            // failed to retrieve gadget, or failed parsing it
                            error = gadget["errors"] as string;
                        }
                        else
                        {
                            // retrieved and parsed gadget ok, store it in db
                            info = db.applications.Where(a => a.url == appUrl).SingleOrDefault();
                            if (info == null)
                            {
                                info = new application();
                                db.applications.InsertOnSubmit(info);
                            }
                            info.url = (string)gadget["url"];
                            info.title = gadget["title"] == null ? "" : (string)gadget["title"];
                            info.directory_title = gadget["directoryTitle"] == null ? "" : (string)gadget["directoryTitle"];
                            info.height = string.IsNullOrEmpty(gadget["height"] as string) ? 0 : int.Parse(gadget["height"].ToString());
                            info.screenshot = gadget["screenshot"] == null ? "" : (string)gadget["screenshot"];
                            info.thumbnail = gadget["thumbnail"] == null ? "" : (string)gadget["thumbnail"];
                            info.author = gadget["author"] == null ? "" : (string)gadget["author"];
                            info.author_email = gadget["author_email"] == null ? "" : (string)gadget["author_email"];
                            info.description = gadget["description"] == null ? "" : (string)gadget["description"];
                            info.settings = gadget["userPrefs"] == null ? "" : gadget["userPrefs"].ToString();
                            info.views = gadget["views"] == null ? "" : gadget["views"].ToString();
                            info.scrolling = gadget["scrolling"] == null ? "no" : gadget["scrolling"].ToString() == "true" ? "yes" : "no";
                            info.type = (int)type;
                            // extract the version from the iframe url
                            string iframeUrl = (string)gadget["iframeUrl"];
                            info.version = "";
                            if (!String.IsNullOrEmpty(iframeUrl) && iframeUrl.IndexOf('?') != -1)
                            {
                                var iframeParsed = HttpUtility.ParseQueryString(iframeUrl.Substring(iframeUrl.IndexOf('?') + 1));
                                info.version = iframeParsed.Get("v");
                            }
                            info.modified = DateTime.UtcNow.Ticks;
                            db.SubmitChanges();
                            app.application = info;

                            var existing = from a in db.applications
                                           where a.url == info.url
                                           select a.id;
                            if (existing.Count() == 0)
                            {
                                error = "Could not store application in registry";
                            }
                            else
                            {
                                invalidate_dependency("applications", "all");
                            }
                        }
                    }
                }
            }

            app.application = info;
            app.error = error;

            if (error == "")
            {
                add_dependency("applications", info.id.ToString(), null);
                Insert("applications", info.id.ToString(), app);
            }

            return app;
        }
#endif


        private JsonObject fetch_gadget_metadata(string appUrl)
	    {
	        JsonObject loc = new JsonObject();
	        loc.Put("country", "US");
            loc.Put("language", "en");
            loc.Put("view", "default");
            loc.Put("container", RayaSettings.Container);
            JsonObject gadgets = new JsonObject();
	        gadgets.Put("url", appUrl);
            gadgets.Put("moduleId", "1");
            JsonObject request = new JsonObject();
            request.Put("context", loc);
            request.Accumulate("gadgets", gadgets);
	        string content = "";
            HttpWebRequest webreq = (HttpWebRequest)WebRequest.Create(RayaSettings.Gadget_server + "/gadgets/metadata");
	        //webreq.Timeout = 20*1000;
	        webreq.AllowAutoRedirect = true;
	        webreq.MaximumAutomaticRedirections = 10;
	        webreq.Method = "POST";
	        Stream reqStream = webreq.GetRequestStream();
	        byte[] data = Encoding.UTF8.GetBytes(HttpUtility.UrlEncode(request.ToString()));
            reqStream.Write(data,0,data.Length);
            reqStream.Close();
	        try
	        {
                WebResponse webresp = webreq.GetResponse();
                if (webresp != null)
                {
                    using (StreamReader reader = new StreamReader(webresp.GetResponseStream()))
                    {
                        content = reader.ReadToEnd();
                    }
                    webresp.Close();
                }
	        }
	        catch 
	        {
	            return null;
	        }
    	    
	        return JsonConvert.Import(content) as JsonObject;
	    }

        public Application get_application_by_url(string url)
        {
            object data;
            if (!TryGetCache("applications", url, out data))
            {
                data = get_application(url);
                Insert("applications", url, data);
                //add_dependency("person_applications", _id.ToString());
                add_dependency("applications", url, null);
            }
            return (Application)data;
        }

        

        private Application get_application(string appUrl)
        {
            return get_application(appUrl, appType.external);
        }

        
    }
}
